package com.yfbao.horizon.encrypt.utils;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA加密与解密工具类
 * 非对称加密
 */
@Slf4j
public class RSAUtils {
    /**
     * 加密算法RSA
     */
    private static final String RSA = "RSA";
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;
    /**
     * key长度
     */
    private static final int DEFAULT_KEY_SIZE = 1024;
    private static final String EN_CODING = "UTF-8";


    /**
     * 生成密钥对
     *
     * @return
     */
    public static KeyPair generateKey() {
        try {
            KeyPairGenerator pairgen = KeyPairGenerator.getInstance(RSA);
            SecureRandom random = new SecureRandom();
            pairgen.initialize(DEFAULT_KEY_SIZE, random);
            return pairgen.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将密钥采用Base64加密并返回加密后的密文
     *
     * @param key 密钥
     * @return
     */
    public static String keyEncrypt(Key key) {
        return new String(Base64.encodeBase64(key.getEncoded()));
    }

    /**
     * 利用公钥进行加密
     *
     * @param clearText                     明文
     * @param publicKey(请使用keyEncrypt转换key) 公钥
     * @return
     */
    public static String encrypt(byte[] clearText, PublicKey publicKey) {
        return encrypt(clearText, keyEncrypt(publicKey));
    }

    /**
     * 直接利用公钥加密
     *
     * @param clearText 明文
     * @param publicKey 公钥
     * @return
     */
    public static String encrypt(String clearText, PublicKey publicKey) {
        try {
            return encrypt(clearText.getBytes(EN_CODING), keyEncrypt(publicKey));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 直接利用公钥加密
     *
     * @param clearText 明文
     * @param publicKey 公钥
     * @return
     */
    public static String encrypt(String clearText, String publicKey) throws Exception {
        return encrypt(clearText.getBytes(EN_CODING), publicKey);
    }

    /**
     * 利用公钥进行加密
     *
     * @param clearText                     明文
     * @param publicKey(请使用keyEncrypt转换key) 公钥
     * @return
     */
    public static String encrypt(byte[] clearText, String publicKey) {
        try {
            // 对公钥进行base64编码
            byte[] decoded = Base64.decodeBase64(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(decoded));
            // RSA加密
            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            int inputLen = clearText.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(clearText, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(clearText, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            return Base64.encodeBase64String(out.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 利用私钥解密
     *
     * @param cipherText                     密文
     * @param privateKey(请使用keyEncrypt转换key)
     * @return 明文byte[]
     */
    public static byte[] decrypt2Byte(String cipherText, String privateKey) {
        try {
            // 64位解码加密后的字符串
            byte[] inputByte = Base64.decodeBase64(cipherText.getBytes(EN_CODING));
            // base64编码的私钥
            byte[] decoded = Base64.decodeBase64(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(RSA)
                    .generatePrivate(new PKCS8EncodedKeySpec(decoded));
            // RSA解密
            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            int inputLen = inputByte.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(inputByte, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(inputByte, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 利用私钥解密
     *
     * @param cipherText                     密文
     * @param privateKey(请使用keyEncrypt转换key)
     * @return 明文
     */
    public static String decrypt(String cipherText, String privateKey) {
        return new String(decrypt2Byte(cipherText, privateKey));
    }

    /**
     * 直接利用私钥解密
     *
     * @param cipherText 密文
     * @param privateKey 私钥
     * @return
     */
    public static String decrypt(String cipherText, PrivateKey privateKey) {
        return decrypt(cipherText, keyEncrypt(privateKey));
    }

    /**
     * 直接利用私钥解密
     *
     * @param cipherText 密文
     * @param privateKey 私钥
     * @return 明文byte[]
     */
    public static byte[] decrypt2Byte(String cipherText, PrivateKey privateKey) {
        return decrypt2Byte(cipherText, keyEncrypt(privateKey));
    }

}